<script lang="ts" setup>
import {
  computed,
  defineAsyncComponent,
  defineEmits,
  nextTick,
  onMounted,
  ref,
  unref,
  useAttrs,
  watch,
} from 'vue';

import { cloneDeep, get, isEqual, isFunction } from '@vben/utils';

import { objectOmit, useDebounceFn } from '@vueuse/core';

const props = withDefaults(defineProps<Props>(), {
  labelField: 'label',
  valueField: 'value',
  resultField: 'list',
  searchField: 'searchText',
  numberToString: false,
  params: () => ({}),
  immediate: true,
  loadingSlot: '',
  beforeFetch: undefined,
  afterFetch: undefined,
  autoSelect: false,
  options: () => [],
  pageSize: 20,
  showSearch: true,
  scrollThreshold: 20,
});

const emit = defineEmits<{
  (e: 'optionsChange', options: OptionsItem[]): void;
  (e: 'scrollToBottom'): void;
}>();

const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));

type OptionsItem = {
  [name: string]: any;
  children?: OptionsItem[];
  disabled?: boolean;
  label?: string;
  value?: string;
};

interface Props {
  /** 是否将value从数字转为string */
  numberToString?: boolean;
  /** 获取options数据的函数 */
  api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;
  /** 传递给api的参数 */
  params?: Record<string, any>;
  /** 从api返回的结果中提取options数组的字段名 */
  resultField?: string;
  /** label字段名 */
  labelField?: string;
  /** value字段名 */
  valueField?: string;
  /** 查询字段的名称 */
  searchField?: string;
  /** 是否立即调用api */
  immediate?: boolean;
  /** 在api请求之前的回调函数 */
  beforeFetch?: (params: any) => Promise<any>;
  /** 在api请求之后的回调函数 */
  afterFetch?: (res: any) => Promise<any>;
  /** 直接传入选项数据，也作为api返回空数据时的后备数据 */
  options?: OptionsItem[];
  /** 组件的插槽名称，用来显示一个"加载中"的图标 */
  loadingSlot?: string;
  /**
   * 自动选择
   * - `first`：自动选择第一个选项
   * - `last`：自动选择最后一个选项
   * - `one`: 当请求的结果只有一个选项时，自动选择该选项
   * - 函数：自定义选择逻辑，函数的参数为请求的结果数组，返回值为选择的选项
   * - false：不自动选择(默认)
   */
  autoSelect?:
    | 'first'
    | 'last'
    | 'one'
    | ((item: OptionsItem[]) => OptionsItem)
    | false;
  /** 每页条数 */
  pageSize?: number;
  /** 是否显示搜索框 */
  showSearch?: boolean;
  /** 滚动到底部触发加载的阈值（像素） */
  scrollThreshold?: number;
}

const modelValue = defineModel<any>({ default: undefined });

const attrs = useAttrs();
const innerParams = ref({});
const refOptions = ref<OptionsItem[]>([]);
const loading = ref(false);
const isFirstLoaded = ref(false);
const currentPage = ref(1);
const totalPages = ref(0);
const totalItems = ref(0);
const searchValue = ref('');
const selectRef = ref();
const hasInitialValueLoaded = ref(false);
const isOpen = ref(false);
const getOptions = computed(() => {
  const { labelField, valueField, numberToString } = props;

  const refOptionsData = unref(refOptions);

  function transformData(data: OptionsItem[]): OptionsItem[] {
    return data.map((item) => {
      const value = get(item, valueField);
      return {
        ...objectOmit(item, [labelField, valueField]),
        label: get(item, labelField),
        value: numberToString ? `${value}` : value,
      };
    });
  }

  const data: OptionsItem[] = transformData(refOptionsData);

  return data.length > 0 ? data : props.options;
});

const bindProps = computed(() => {
  return {
    modelValue: unref(modelValue),
    options: unref(getOptions),
    'onUpdate:modelValue': (val: string) => {
      modelValue.value = val;
    },
    ...objectOmit(attrs, ['onUpdate:modelValue']),
    showSearch: props.showSearch,
    // 禁用前端过滤，使用后端搜索
    filterOption: false,
  };
});

// 滚动处理
const handlePopupScroll = useDebounceFn((e: Event) => {
  const target = e.target as HTMLElement;
  const { scrollTop, scrollHeight, clientHeight } = target;
  const reachBottom =
    scrollHeight - scrollTop <= clientHeight + props.scrollThreshold;

  if (reachBottom && !loading.value && currentPage.value < totalPages.value) {
    fetchApi(currentPage.value + 1);
    emit('scrollToBottom');
  }
}, 200);

// 搜索处理
const handleSearch = useDebounceFn((value: string) => {
  searchValue.value = value;
  resetData();
  fetchApi(1);
}, 300);
const onDropdownVisibleChange = (open: boolean) => {
  if (open) {
    isOpen.value = true;
    if (!isFirstLoaded.value) {
      resetData();
      fetchApi(1);
    }
  } else {
    isOpen.value = false;
  }
};
// 重置数据
function resetData() {
  refOptions.value = [];
  currentPage.value = 1;
  totalPages.value = 0;
  totalItems.value = 0;
}
async function loadInitialValue() {
  const { api, resultField, pageSize } = props;
  if (!api || !isFunction(api)) return;

  const currentValue = unref(modelValue);
  if (currentValue === undefined || currentValue === null) return;
  if (bindProps.value.mode === 'multiple' && currentValue.length <= 0) {
    return;
  }

  try {
    const finalParams = {
      pageNo: 1,
      pageSize,
    } as any;
    if (
      bindProps.value.mode === 'multiple' ||
      bindProps.value.mode === 'tags'
    ) {
      finalParams[`${props.valueField}List`] = currentValue;
    } else {
      finalParams[props.valueField] = currentValue;
    }

    // 发起请求
    const res = await api(finalParams);

    // 提取数据
    let items: OptionsItem[] = [];
    if (Array.isArray(res)) {
      items = res;
    } else {
      items = resultField ? get(res, resultField, []) : [];
    }
    refOptions.value = items;
  } catch (error) {
    console.error('ApiPagingSelect load error:', error);
  } finally {
    hasInitialValueLoaded.value = true;
    isFirstLoaded.value = false;
  }
}
// 加载数据
async function fetchApi(pageNo: number) {
  const { api, beforeFetch, afterFetch, resultField, pageSize } = props;

  if (!api || !isFunction(api)) return;

  try {
    loading.value = true;
    let finalParams = {
      ...unref(innerParams),
      ...props.params,
      pageNo,
      pageSize,
    } as any;
    finalParams[props.searchField] = searchValue.value;
    // 请求前处理
    if (beforeFetch && isFunction(beforeFetch)) {
      finalParams = (await beforeFetch(cloneDeep(finalParams))) || finalParams;
    }

    // 发起请求
    let res = await api(finalParams);
    // 请求后处理
    if (afterFetch && isFunction(afterFetch)) {
      res = (await afterFetch(res)) || res;
    }

    isFirstLoaded.value = true;

    // 提取数据
    let items: OptionsItem[] = [];
    if (Array.isArray(res)) {
      items = res;
      totalItems.value = res.length;
    } else {
      items = resultField ? get(res, resultField, []) : [];
      totalItems.value = get(res, 'total', items.length);
    }

    // 计算总页数
    totalPages.value = Math.ceil(totalItems.value / pageSize);

    // 合并数据
    refOptions.value = pageNo === 1 ? items : [...unref(refOptions), ...items];

    currentPage.value = pageNo;

    emitChange();
  } catch (error) {
    console.error('ApiPagingSelect load error:', error);
  } finally {
    loading.value = false;
    // 确保DOM更新后滚动条位置正确
    await nextTick();
  }
}

function emitChange() {
  if (
    (modelValue.value === undefined || modelValue.value === null) &&
    props.autoSelect &&
    unref(getOptions).length > 0
  ) {
    let firstOption;
    if (isFunction(props.autoSelect)) {
      firstOption = props.autoSelect(unref(getOptions));
    } else {
      switch (props.autoSelect) {
        case 'first': {
          firstOption = unref(getOptions)[0];
          break;
        }
        case 'last': {
          firstOption = unref(getOptions)[unref(getOptions).length - 1];
          break;
        }
        case 'one': {
          if (unref(getOptions).length === 1) {
            firstOption = unref(getOptions)[0];
          }
          break;
        }
      }
    }

    if (firstOption) modelValue.value = firstOption.value;
  }
  emit('optionsChange', unref(getOptions));
}

const mergedParams = computed(() => {
  return {
    ...props.params,
    ...unref(innerParams),
  };
});
// 监听modelValue变化
watch(
  modelValue,
  (newVal, oldVal) => {
    if (newVal !== oldVal && !isOpen.value) {
      loadInitialValue();
    }
  },
  { immediate: true },
);

// 组件挂载后检查是否需要加载初始值
onMounted(() => {
  if (!hasInitialValueLoaded.value) {
    loadInitialValue();
  }
  if (props.autoSelect) {
    fetchApi(1);
  }
});

// 监听参数变化
watch(
  mergedParams,
  (value, oldValue) => {
    if (isEqual(value, oldValue)) return;
    resetData();
    fetchApi(1);
  },
  { deep: true, immediate: false },
);
defineExpose({
  /** 获取options数据 */
  getOptions: () => unref(getOptions),
  /** 获取当前值 */
  getValue: () => unref(modelValue),
  /** 获取Select组件实例 */
  getSelectRef: () => selectRef.value,
  /** 更新Api参数 */
  updateParam: (newParams: Record<string, any>) => {
    innerParams.value = newParams;
  },
  /** 手动加载数据 */
  reload: () => {
    resetData();
    fetchApi(1);
  },
  /** 重置搜索 */
  resetSearch: () => {
    searchValue.value = '';
    resetData();
    fetchApi(1);
  },
});
</script>

<template>
  <Select
    ref="selectRef"
    v-bind="bindProps"
    :options="getOptions"
    :loading="loading"
    @popup-scroll="handlePopupScroll"
    @search="handleSearch"
    @dropdown-visible-change="onDropdownVisibleChange"
  >
    <template v-if="loading && $slots.loading" #loading>
      <slot name="loading"></slot>
    </template>
    <template v-else-if="$slots.notFoundContent" #notFoundContent>
      <slot name="notFoundContent"></slot>
    </template>
    <template v-for="item in Object.keys($slots)" #[item]="data">
      <slot :name="item" v-bind="data || {}"></slot>
    </template>
  </Select>
</template>
